package com.google.code.fluentjoiner;

import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import java.io.Serializable;
import java.util.Iterator;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Lowest level class in the hierarcy responsible for most simple Supplier
 * construction.
 * @author Sean Parsons
 * @param <T> Type the Supplier instance returns.
 */
public abstract class SupplierBuilder<T> implements Serializable
{
    private static final Logger LOGGER = LoggerFactory.getLogger(SupplierBuilder.class);

    /**
     * Abstract method to implement in subclasses of SupplierBuilder that returns
     * the Supplier instance created by this class.
     * @return Instance of Supplier with parameterized type <T>.
     */
    public abstract Supplier<T> getSupplier();

    /**
     * Returns the object as built by the Supplier instance.
     * @return Instance of T.
     */
    public T get()
    {
        return getSupplier().get();
    }

    /**
     * Creates a Callable instance that delegates the call method to the get() method on this class.
     * @return An instance of Callable<T> that returns the value.
     */
    public Callable<T> getCallable()
    {
        return new SerializableCallable<T>()
        {
            @Override
            public T call() throws Exception
            {
                return get();
            }
        };
    }

    /**
     * Builds a SupplierBuilder that returns the Supplier passed in.
     * @param <T> Type for the SupplierBuilder and from the Supplier.
     * @param supplier Supplier to return.
     * @return Returns the Supplier passed in from the parameter supplier.
     */
    public static <T> SupplierBuilder<T> fromSupplier(final Supplier<T> supplier)
    {
        return new SupplierBuilder<T>()
        {
            @Override
            public Supplier<T> getSupplier()
            {
                return supplier;
            }
        };
    }

    /**
     * Builds a SupplierBuilder that returns a Supplier that in turn returns the value passed in.
     * @param <T> Type for the SupplierBuilder and from the value.
     * @param value Value to be returned.
     * @return Returns a Supplier which returns the value passed in.
     */
    public static <T> SupplierBuilder<T> fromValue(final T value)
    {
        return new SupplierBuilder<T>()
        {
            @Override
            public Supplier<T> getSupplier()
            {
                return Suppliers.ofInstance(value);
            }
        };
    }

    /**
     * Builds a SupplierBuilder that takes its value from a Callable passed in.
     * @param <T> Type for the SupplierBuilder and from the Callable.
     * @param callable Callable to call.
     * @return Returns a Supplier which returns the value in turn returned from the Callable.
     */
    public static <T> SupplierBuilder<T> fromCallable(final Callable<T> callable)
    {
        return new AbstractSupplierBuilder<T>()
        {
            @Override
            protected T getSupplierResult()
            {
                try
                {
                    return callable.call();
                }
                catch (final Exception exception)
                {
                    throw new RuntimeException("Exception thrown when calling call in fromCallable.", exception);
                }
            }
        };
    }

    /**
     * Returns a new SupplierBuilder with a transformed value Supplier instance.
     * @param <U> The type for the Supplier to return.
     * @param function to transform from the type T or a superclass to an instance of type U.
     * @return Returns a Supplier that returns the transformed value.
     */
    public <U> SupplierBuilder<U> transform(final Function<? super T, U> function)
    {
        final Supplier<T> supplier = getSupplier();
        return new SupplierBuilder<U>()
        {
            @Override
            public Supplier<U> getSupplier()
            {
                return Suppliers.compose(function, supplier);
            }
        };
    }

    /**
     * Returns an IteratorSupplierBuilder for the Iterator return from the function passed in.
     * @param <U> The type parameter of the Iterator
     * @param function Function implementation to transform the value from this SupplierBuilder
     * into an iterator with parameterized type U.
     * @return Instance of an IteratorSupplierBuilder for the Iterator returned from the function.
     */
    public <U> IteratorSupplierBuilder<U> map(final Function<? super T, Iterator<U>> function)
    {
        final Supplier<T> supplier = getSupplier();
        return new IteratorSupplierBuilder<U>()
        {
            @Override
            protected Iterator<U> getSupplierResult()
            {
                return function.apply(supplier.get());
            }
        };
    }

    /**
     * Returns an ApplierSupplierBuilder based on the Supplier from this class and the Consumer
     * passed in.
     * @param consumer The Consumer to become part of the Applier.
     * @return Instance of ApplierSupplierBuilder.
     */
    public ApplierSupplierBuilder<T> toApplierSupplierBuilder(final Consumer<? super T> consumer)
    {
        final Supplier<T> supplier = getSupplier();
        return ApplierSupplierBuilder.create(supplier, consumer);
    }

    /**
     * Returns an instance of StringSupplierBuilder based on this instance.
     * @return Instance of StringSupplierBuilder.
     */
    public StringSupplierBuilder toStringSupplierBuilder()
    {
        return StringSupplierBuilder.fromSupplierBuilder(this);
    }
}